package com.sz.biz.logistics.serialno.algorithm.impl;

import com.sz.biz.logistics.serialno.algorithm.VerificationCodeAlgorithm;
import com.sz.biz.logistics.serialno.constants.BillNumberConstants;
import com.sz.biz.logistics.serialno.constants.BillNumberErrorCodes;
import com.sz.biz.logistics.serialno.dto.AlgorithmInfoDto;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.utils.StringUtils;

import javax.annotation.Nullable;

/**
 * Package: com.sz.biz.logistics.channelno.algorithm.impl<br>
 * Description: 限定权值数组长度的加权校验算法<br>
 * @author WuHan<br>
 * @date 2017-12-09 22:07<br>
 * @serial
 * @since sys-1.1.3
 */
public class RestrictedWeightArrayAlgorithm implements VerificationCodeAlgorithm {
    
    /**
     * 支持验证的序列长度，不含前、后缀等不可变部分
     */
    private static int validateLength = 8;
    
    /**
     *
     */
    private static final int VERIFYCODE_INDEX = 8;
    
    /**
     * 算法信息DTO
     */
    private static AlgorithmInfoDto algorithmInfoDto = new AlgorithmInfoDto();
    
    /**
     * 权重数组
     */
    private int[] weightArray;
    
    //DTO初始化
    static {
        algorithmInfoDto.setCode(BillNumberConstants.AlgorithmInfo.RESTRICTED_WEIGHT_ARRAY_ALGORITHM);
        algorithmInfoDto.setName("限定验证长度为" + validateLength + "位的加权校验算法");
        algorithmInfoDto.setValidateLength(validateLength);
        algorithmInfoDto.setVerifyCodeIndex(VERIFYCODE_INDEX);
        algorithmInfoDto.setExample("123456**?");
        algorithmInfoDto.setType(BillNumberConstants.NumberType.CHANNEL_NUMBER);
    }
    
    /**
     * 返回算法信息供工厂方法调用
     * @return 算法信息
     */
    public static AlgorithmInfoDto getAlgorithmInfoDto() {
        return algorithmInfoDto;
    }
    
    /**
     * 返回算法可支持的校验长度
     * @return 可校验长度
     */
    public static int getValidateLength() {
        return validateLength;
    }
    
    /**
     * 设定长度的校验算法构造方法
     * @param length 需要校验的长度，系统跟据此长度自动生成权重数组
     */
    public RestrictedWeightArrayAlgorithm(int length) {
        validateLength = length;
        this.weightArray = new int[validateLength];
        for (int i = 0; i < validateLength; i++) {
            this.weightArray[i] = i % 2 == 0 ? i + 2 : this.weightArray.length + 2 - i;
        }
    }
    
    /**
     * 默认的校验算法构造方法
     */
    public RestrictedWeightArrayAlgorithm() {
        validateLength = 8;
        this.weightArray = new int[validateLength];
        for (int i = 0; i < validateLength; i++) {
            this.weightArray[i] = i % 2 == 0 ? i + 2 : this.weightArray.length + 2 - i;
        }
    }
    
    /**
     * 返回算法信息
     */
    @Override
    public void getAlgorithmInfo() {
    
    }
    
    /**
     * 生成校验位
     * @param rawString 原始串号,如"ABB0123456?C"
     * @param pattern 待生成校验位的模式串,如"AB#*******?C"
     * @param formatRule 格式规则，当模式串形如 ABC123456?D-ABC234567?D 时，<br>无法判断某一位置具体是纯数字/纯字母还是混合类型，<br>为防止范围计算出错，加入基础原则进行辅助判断。
     * @return 带有校验位数字的串号
     */
    @Override
    public String generateVerificationCode(String rawString, @Nullable String pattern, String formatRule) {
        if(StringUtils.isEmpty(formatRule)){
            throw new RuntimeException("formatRule 为空");
        }
        formatRule = formatRule.replaceAll(BillNumberConstants.Characters.BLANK, BillNumberConstants.Characters.EMPTY_STRING);
    
        int count = 0;
        try {
            if (StringUtils.isEmpty(rawString)) {
                throw new IllegalArgumentException("初始串为空！");
            }
            
            if (!StringUtils.isEmpty(pattern)) {
                if (rawString.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE) != pattern.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)) {
                    throw Exceptions.bizException(BillNumberErrorCodes.ERROR_CHANNEL_RULE_VERIFY_POSITION_DISLOCATED);
                }
    
                pattern = restoreVerifyPosForFormatRule(pattern, formatRule, pattern.contains(BillNumberConstants.Symbols.SYMBOL_SEPERATOR));
    
                if (rawString.contains(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)) {
                    if (rawString.substring(0, rawString.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)).length() < validateLength) {
                        throw Exceptions.bizException(BillNumberErrorCodes.ERROR_CHANNEL_RULE_PATTERN_LENGTH_MISMATCH);
                    }
                    int pos = rawString.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE);
                    for (int i = pos - validateLength, j = 0; i < pos && j <= validateLength; i++) {
                        int charCode = BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get(rawString.charAt(i)) - BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get('0') == 0
                                       ? (rawString.length() - i) % BillNumberConstants.PatternParse.NUMBER_BASE
                                       : BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get(rawString.charAt(i)) - BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get('0');
                        count += charCode * weightArray[j];
                        j++;
                    }
                }
            } else {
                if (rawString.contains(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)) {
                    if (rawString.substring(0, rawString.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)).length() < validateLength) {
                        throw Exceptions.bizException(
                                BillNumberErrorCodes.ERROR_CHANNEL_RULE_PATTERN_LENGTH_MISMATCH);
                    }
                }
                int pos = rawString.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE);
                for (int i = pos - validateLength, j = 0; i < pos && j <= validateLength; i++) {
                    if (rawString.substring(i, i + 1).matches(BillNumberConstants.Regexs.PURE_DIGIT)) {
                        int charCode = BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get(rawString.charAt(i)) - BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get('0') == 0 ?
                                (rawString.length() - i) % BillNumberConstants.PatternParse.NUMBER_BASE
                                : BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get(rawString.charAt(i)) - BillNumberConstants.PatternParse.CHARACTER_INTEGER_MAP.get('0');
                        count += charCode * weightArray[j];
                        j++;
                    }
                }
            }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        return rawString.replace(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE, String.valueOf(count % 10));
    }
    
    /**
     * 单号的正确性验证
     * @param channelNumber 待验的单号,如"ABB01234565C"
     * @param pattern 待生成校验位的模式串,如"AB#*******?C"
     * @param formatRule 格式规则，当模式串形如 ABC123456?D-ABC234567?D 时，<br>无法判断某一位置具体是纯数字/纯字母还是混合类型，<br>为防止范围计算出错，加入基础原则进行辅助判断。
     * @return 是否符合校验规则
     */
    @Override
    public boolean validityCheck(String channelNumber, String pattern, String formatRule) {
        if (!pattern.contains(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)) {
            throw new IllegalArgumentException("模式串不包含校验位!");
        }
        StringBuilder sb = new StringBuilder();
        sb.append(channelNumber.substring(0, pattern.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)))
          .append(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE)
          .append(channelNumber.substring(pattern.indexOf(BillNumberConstants.Symbols.SYMBOL_VERIFYCODE) + 1));
        return channelNumber.equals(generateVerificationCode(sb.toString(), pattern, formatRule));
    }

    @Override
    public boolean checkSerialNumber(String serialNumber) {
        return !StringUtils.isEmpty(serialNumber) && this.checkValidityLength(serialNumber, validateLength);
    }
}
